Terraform の ステートファイルを Cloud Storage で管理する #cm_google_cloud_adcal_2024

Terraform の ステートファイルを Cloud Storage で管理する #cm_google_cloud_adcal_2024

Terraform のステートファイル管理を Cloud Storage で実現する方法と挙動についてまとめました。
Clock Icon2024.12.05

はじめに

クラスメソッドの Google Cloud Advent Calendar 2024 の 5 日目のブログです!
Google Cloud Advent Calendar 2024 では Google Cloud が大好きな弊社エンジニアが技術ブログを持ち回りで毎日執筆中ですので、ご覧になっていただけると嬉しいです。
https://qiita.com/advent-calendar/2024/cm-google-cloud

本日は Cloud Storage を利用した Terraform ステートファイル管理 について解説いたします。

ステートファイルと管理の課題について

Terraform を実行してリソースを作成すると、どのようなリソースを Terraform で作成したかの情報が ステートファイル に保存されます。デフォルトでステートファイルは、 Terraform を実行したディレクトリに terraform.tfstate というファイル名で作成されます。Terraform を実行する度に、変更した Terraform コードとステートファイルを比較し、追加 / 変更 / 削除するリソースを判断しています。

個人で Terraform を実行する分には、ステートファイルはローカル環境に保存しておけば特段課題はありません。しかし、チーム開発のケースにおいてはステートファイルの管理において以下のような考慮をする必要があります。

  • 複数メンバーが同じステートファイルを共有できるようにする
  • 複数メンバーが 同時に Terraform を実行した際にステートファイルの更新や参照で競合が起きないようステートファイルをロックする仕組みを導入する

ステートファイルの管理における課題や管理方法の選択肢については以下ブログでも紹介していますのでご参照ください。

https://dev.classmethod.jp/articles/terraform_tfstate_management_tfc/

Cloud Storage でステートファイルを管理する

Google Cloud リソースを Terraform で作成するケースにおいて、 Cloud Storage でステートファイルを管理することで以下のメリットを享受できます。

  • IAM で管理されたプリンシパルと IAM ロールによる認証/認可により、Google Cloud プロジェクト内の開発チームでのファイル共有を容易に実現できる
  • Terraform は Cloud Storage 単体でのステートファイルのロック機能を提供しているため、Amazon S3 でステートファイルを管理する際に必要であった DynamoDB を利用したロック機能の構成を準備しなくてよい(※)

※ Terraform 1.10 より experimental な機能として Amazon S3 の "lockfile" を利用したロックが提供されました。DynamoDB を利用したロックのための構成は将来的に不要となりそうです。詳細は以下ご参照ください。
https://github.com/hashicorp/terraform/blob/5279e432611752a9bcba27baaf657a3305c226f8/website/docs/language/backend/s3.mdx#state-locking

Locking can be enabled via an S3 "lockfile" (introduced as experimental in Terraform 1.10) or DynamoDB. To support migration from older versions of Terraform which only support DynamoDB-based locking, the S3 and DynamoDB arguments below can be configured simultaneously. In a future minor version the DynamoDB locking mechanism will be removed.

Amazon S3 と DynamoDB を組み合わせたステートファイルの管理とロックのための構成については以下をご参照ください。
https://developer.hashicorp.com/terraform/language/backend/s3

やってみる

Cloud Shell には Terraform がプリインストールされていますので、本検証では Cloud Shell から Terraform を実行していきます。検証の中で、ステートファイルを Cloud Storage で管理するための設定や挙動について確認してみたいと思います。

Terraform で作成するリソースについては、VPC(default) に Compute Engine VM インスタンスを起動する簡易的な構成とします。

また、説明のしやすさを勘案して以下の流れで検証を実施します。

  • Cloud Storage バケットは事前に Cloud SDK (gcloud コマンド)を利用して作成
  • Terraform 設定ファイルにバックエンド(ステートファイル管理方法の設定)として Cloud Storage バケットを指定
  • Terraform を実行しステートファイルの参照やロックの挙動を確認

実際の運用ではステート管理用の Cloud Storage バケットも Terraform で一元管理するのがよいでしょう。詳細は以下をご参照ください。
https://cloud.google.com/docs/terraform/resource-management/store-state?hl=ja

権限について

このあとの手順で、ステートファイルを管理するための Cloud Storage バケットを作成しますが、Cloud Storage バケットの作成には ストレージ管理者 (roles/storage.admin) の IAM ロールが必要です。

Cloud Shell から Terraform を実行すると、デフォルトでは実行した IAM ユーザに付与された権限で Terraform から各種リソースを操作します。
そのため、Terraform から Cloud Storage バケットのステートファイルを参照/編集するため Storage オブジェクト管理者(roles/storage.objectAdmin) の権限、Terraform で Compute Engine VM インスタンスを作成するための Compute インスタンス管理者(v1) (roles/compute.instanceAdmin.v1) の IAM ロールが必要となります。

ただし、本検証ではオーナー権限で手順を進めることとします。

API を有効化する

Cloud Storage バケットの作成と Terraform による Compute Engine VM インスタンスの作成のため、以下 API の有効化が必要となります。

  • Compute Engine API
  • Cloud Storage API

有効化されていない場合、Cloud Shell より必要な API の有効化をします。

$ gcloud services enable compute.googleapis.com
$ gcloud services enable storage.googleapis.com

検証用 Terraform 設定ファイルを用意する

検証用の Terraform 設定ファイル (provider.tf , main.tf, variables.tf) を Cloud Shell 上に用意し gs-state-testディレクトリに配置しておきます。

$ mkdir gs-state-test
$ cd gs-state-test
$ ls
main.tf  provider.tf  variables.tf
provider.tf
provider "google" {
  project = var.project_id
}
main.tf
resource "google_compute_instance" "default" {
  name         = "tf-instance"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-a"

  boot_disk {
    initialize_params {
      image = "projects/debian-cloud/global/images/debian-12-bookworm-v20241112"
      size  = 20
      type  = "pd-balanced"
    }
  }

  network_interface {
    network = "default"
  }
}
variables.tf
variable "project_id" {
  type    = string
}

Cloud Storage バケットを作成する

Cloud Shell から以下コマンドでステートファイル管理用の Cloud Storage バケットを作成します。

$ export PROJECT_ID=$(gcloud config get-value project)
$ gcloud storage buckets create gs://gs-state-test-${PROJECT_ID}/ \
--location=asia-northeast1 \
--public-access-prevention

Cloud Storage を Terraform のバックエンドに指定

Terraform のバックエンドとは、ステートファイルの保管方法に関する設定をす項目です。デフォルトだとローカルに保存するローカルバックエンドの動作となりますが、Cloud Storage や S3 にステートファイルを保存する場合、リモートバックエンドとして保管先を指定します。

main.tf に以下を追加します。

main.tf
terraform {
  backend "gcs" {
    bucket  = "<BUCKET_NAME>"
    prefix  = "terraform/state"
  }
}
~~~

Terraform 自体の設定であるため terraform ブロックにバックエンド設定を記述します。
backend: バックエンドの名前を指定します。
bucket: Cloud Storage バケットを指定します。今回の場合、"gs-state-test-<PROJECT_ID>" を指定しました。
prefix: バケット内の保存先を指定します。

構文の詳細は以下をご参照ください。
https://developer.hashicorp.com/terraform/language/backend/gcs#example-configuration

Terraform を実行してみる

まずは Terraform を初期化します。

terraform init

以下のようにバックエンドの設定を初期化するログが表示されました。

Initializing the backend...

Successfully configured the backend "gcs"! Terraform will automatically
use this backend unless the backend configuration changes.

次に Terraform を実行します。

terraform apply

Terraform 実行後、バックエンドとして指定したバケットを確認してみると default.tfstate というファイル名でステートファイルが生成されていることがわかります。

$ gsutil ls gs://gs-state-test-${PROJECT_ID}/terraform/state
gs://gs-state-test-<PROJECT_ID>/terraform/state/default.tfstate

以下コマンドでステートファイルの内容が確認できます。

$ gsutil cat gs://gs-state-test-${PROJECT_ID}/terraform/state/default.tfstate

ロック動作を試してみる

Terraform 実行時にステートファイルが正しくロックされるか確認してみます。まずは terraform apply を実行します。

$ terraform apply 

上記の Terraform 実行中に別のユーザで Terraform を実行すると以下のようになります。

$ terraform apply
Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: writing "gs://gs-state-test-<PROJECT_ID>/terraform/state/default.tflock" failed: googleapi: Error 412: At least one of the pre-conditions you specified did not hold.,
│ conditionNotMet
│ Lock Info:
│   ID:        1733385132804458
│   Path:      gs://gs-state-test-da-<PROJECT_ID>/terraform/state/default.tflock
│   Operation: OperationTypeApply
│   Who:       <USER>
│   Version:   1.5.7
│   Created:   2024-12-05 07:52:12.515160731 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.

Cloud Storage バケットを見てみると、default.tflockというファイルが生成されていることがわかります。

$ gsutil ls -r gs://gs-state-test-da-${PROJECT_ID}/

~~~

gs://gs-state-test-<PROJECT_ID>/terraform/state/default.tflock

この後、最初のユーザでの terraform apply をキャンセルし、Cloud Storage バケットを見てみると、default.tflockというファイルは削除されます。この状態でもう一度別のユーザで terraform apply を実行すると問題無く実行できます。

最後にステートファイルのロックの仕組みについて補足します。
terraform apply を実行すると Terraform はバックエンドにロックファイル.tflockが存在するかを確認します。
ロックファイルが存在しなければ作成し、ステートファイルを参照してリソースをデプロイします。その後、リソースのデプロイが完了すると Terraform はバックエンドからロックファイルを削除します。
バックエンドにロックファイル.tflockが存在すれば、他ユーザがステートファイルを参照していると判断し、Terraform の実行を中断します。

おわりに

Terraform のステートファイルを Cloud Storage で管理する方法とロック機能の動作について説明いたしました。非常に簡単なステップで利用できることがわかったかと思います。

Google Cloud で Terraform を利用する際のベストプラクティスは公式ドキュメントにもまとまっていますので、興味のある方はご覧になってみてください。
https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja

また、今後も Google Cloud x Terraform に関する記事を公開していこうと思いますのでご期待ください。

Google Cloud Advent Calendar 2024 の 明日 12/6 投稿も私 村田一紘 でございます。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.